From 089dafc20f46357fe102ed7c74d45e3aa56ae695 Mon Sep 17 00:00:00 2001 From: Ryan Quattlebaum Date: Mon, 14 Mar 2016 13:22:17 -0400 Subject: [PATCH] Suggest the best matching target for cargo run Targets passed to cargo compile are validated against the package. If the target exists, it is compiled. If not, cargo will bail and offer a suggested target name if there is a close match. The tests create and build/run binaries and examples using filenames that are close (or not so close) to the target names to verify that close matching names are suggested to the user. --- src/cargo/core/package.rs | 13 ++++++++++-- src/cargo/ops/cargo_compile.rs | 29 ++++++++++++++++++++++++++ tests/test_cargo_compile.rs | 38 ++++++++++++++++++++++++++++++++++ tests/test_cargo_run.rs | 38 ++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index c8362ffa2..703fcff83 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -6,10 +6,10 @@ use std::path::{Path, PathBuf}; use semver::Version; -use core::{Dependency, Manifest, PackageId, SourceId, Target}; +use core::{Dependency, Manifest, PackageId, SourceId, Target, TargetKind}; use core::{Summary, Metadata, SourceMap}; use ops; -use util::{CargoResult, Config, LazyCell, ChainError, internal, human}; +use util::{CargoResult, Config, LazyCell, ChainError, internal, human, lev_distance}; use rustc_serialize::{Encoder,Encodable}; /// Information about a package that is available somewhere in the file system. @@ -89,6 +89,15 @@ impl Package { pub fn generate_metadata(&self) -> Metadata { self.package_id().generate_metadata() } + + pub fn find_closest_target(&self, target: &str, kind: TargetKind) -> Option<&Target> { + let targets = self.targets(); + + let matches = targets.iter().filter(|t| *t.kind() == kind) + .map(|t| (lev_distance(target, t.name()), t)) + .filter(|&(d, _)| d < 4); + matches.min_by_key(|t| t.0).map(|t| t.1) + } } impl fmt::Display for Package { diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index ad82d4f77..d5eb7ec09 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -138,6 +138,25 @@ pub fn resolve_dependencies<'a>(root_package: &Package, Ok((packages, resolved_with_overrides)) } +fn validate_target(package: &Package, + name: &str, + kind: TargetKind, + kind_str: &str) -> CargoResult<()> { + let target = package.targets().iter().find(|t: &&Target| { + t.name() == name && *t.kind() == TargetKind::Bin + }); + if target.is_none() { + let suggestion = package.find_closest_target(name, kind); + match suggestion { + Some(s) => bail!("no {} target named `{}`\n\nDid you mean `{}`?", + kind_str, name, s.name()), + None => bail!("no {} target named `{}`", kind_str, name), + } + } + + Ok(()) +} + pub fn compile_pkg<'a>(root_package: &Package, source: Option>, options: &CompileOptions<'a>) @@ -157,6 +176,16 @@ pub fn compile_pkg<'a>(root_package: &Package, bail!("jobs must be at least 1") } + if let CompileFilter::Only{bins, examples, ..} = *filter { + for bin in bins { + try!(validate_target(root_package, bin, TargetKind::Bin, "bin")); + } + + for example in examples { + try!(validate_target(root_package, example, TargetKind::Example, "example")); + } + } + let (packages, resolve_with_overrides) = { try!(resolve_dependencies(root_package, config, source, features, no_default_features)) diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs index 731546e63..35652a196 100644 --- a/tests/test_cargo_compile.rs +++ b/tests/test_cargo_compile.rs @@ -621,6 +621,44 @@ version required: * "#, error = ERROR, proj_dir = p.url()))); }); +test!(cargo_compile_with_filename{ + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + "#) + .file("src/lib.rs", "") + .file("src/bin/a.rs", r#" + extern crate foo; + fn main() { println!("hello a.rs"); } + "#) + .file("examples/a.rs", r#" + fn main() { println!("example"); } + "#); + + assert_that(p.cargo_process("build").arg("--bin").arg("bin.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no bin target named `bin.rs`", error = ERROR))); + + assert_that(p.cargo_process("build").arg("--bin").arg("a.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no bin target named `a.rs` + +Did you mean `a`?", error = ERROR))); + + assert_that(p.cargo_process("build").arg("--example").arg("example.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no example target named `example.rs`", error = ERROR))); + + assert_that(p.cargo_process("build").arg("--example").arg("a.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no example target named `a.rs` + +Did you mean `a`?", error = ERROR))); +}); + test!(compile_path_dep_then_change_version { let p = project("foo") .file("Cargo.toml", r#" diff --git a/tests/test_cargo_run.rs b/tests/test_cargo_run.rs index 8dffc242f..b71073dc9 100644 --- a/tests/test_cargo_run.rs +++ b/tests/test_cargo_run.rs @@ -234,6 +234,44 @@ example sep = SEP))); }); +test!(run_with_filename { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + "#) + .file("src/lib.rs", "") + .file("src/bin/a.rs", r#" + extern crate foo; + fn main() { println!("hello a.rs"); } + "#) + .file("examples/a.rs", r#" + fn main() { println!("example"); } + "#); + + assert_that(p.cargo_process("run").arg("--bin").arg("bin.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no bin target named `bin.rs`", error = ERROR))); + + assert_that(p.cargo_process("run").arg("--bin").arg("a.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no bin target named `a.rs` + +Did you mean `a`?", error = ERROR))); + + assert_that(p.cargo_process("run").arg("--example").arg("example.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no example target named `example.rs`", error = ERROR))); + + assert_that(p.cargo_process("run").arg("--example").arg("a.rs"), + execs().with_status(101).with_stderr(&format!("\ +{error} no example target named `a.rs` + +Did you mean `a`?", error = ERROR))); +}); + test!(either_name_or_example { let p = project("foo") .file("Cargo.toml", r#" -- 2.30.2